require 'sinatra/base'
require 'rack-flash'

# Public: Adds helpers
module Sinatra
  
  module ChassisExtraHelpers
    # Public: Checks if the argument can be evaluated numerically.
    # 
    # Example
    # 
    #   numeric?('1')
    #   # => true
    # 
    #   numeric?('hello')
    #   # => false
    # 
    # Returns true/false.
    def numeric? x
      true if Float(x) rescue false
    end
    
#    define_method(:numeric?) { true if Float(x) rescue false }

    # Public: Truncates a String or Number.
    # 
    #              x - String or Number to truncate
    #     word_count - Integer number of words to return if x is a String (default: 100)
    #     end_string - String appended to the end of the returned String if
    #                  the original x String is longer than word_count (default: '...')
    #        decimal - Integer number of maximum places after decimal if x is a Number (default: 1)
    # trailing_zeros - Boolean to decide if a returned Number should keep the
    #                  trailing 0's as a String (default: false)
    #          round - Boolean to decide if a number should be rounded (default: true)
    # 
    # Examples
    # 
    #   truncate('Lorem ipsum dolor sit amet.', word_count: 3, end_string: '...')
    #   # => 'Lorem ipsum dolor...'
    # 
    #   truncate(1.234000, decimal: 1)
    #   # => 1.2
    #   truncate(1.234000, decimal: 5)
    #   # => 1.234
    #   truncate(1.234000, decimal: 5, trailing_zeros: true)
    #   # => '1.23400'
    # 
    # Returns a String or Float.
    def truncate x, options = {}
      options[:word_count]     ||= 100
      options[:end_string]     ||= '...'
      options[:decimal]        ||= 2
      options[:trailing_zeros] ||= false
      options[:round]          ||= true
      
      if x.kind_of? String
        return if x == nil
        words = x.split()
        return words[0..(options[:word_count]-1)].join(' ') + (words.length > options[:word_count] ? options[:end_string] : '')
      elsif numeric? x
        if options[:round] == true
          number = "%.#{options[:decimal]}f" % x.round(options[:decimal])
        else
          number = "%.#{options[:decimal]}f" % x.to_f
        end
        number = number.to_f unless options[:trailing_zeros]
        return number
      end
    end
    
    # Public: Uppercases the first letter of each word.
    # 
    # Example
    # 
    #   titleize('hello world')
    #   # => 'Hello World'
    # 
    # Returns a String.
    def titleize x
      title = ''
      x.to_s.split(' ').each { |s| title << s.capitalize + ' ' }
      title
    end
    
    # Public: Compares a string to the request path.
    # 
    # path - String to compare against the request path
    # 
    # Exmaple
    # 
    #   Requested URL: http://localhost:4567/hello/world
    #   active('hello')
    #   # => 'active'
    #   active('goodbye')
    #   # => ''
    # 
    # Returns a Sting: 'active' if there's a match, '' if not.
    def active path
      path = Array[path] unless path.kind_of? Array
      match = false
      path.each { |p| match = true if request.path_info.include?(p) }
      'active' if match
    end
    
    # Public: Displays an alert unless flash[:alert] if nil.
    # flash[:alert] empties itself after the first request after it's set.
    # To use, set flash[:alert] to a String.
    # 
    # Example
    #   <%= alert %>
    # 
    # Returns a div with an id of 'alert' and containing the contents
    # of flash[:alert], or if flash[:alert] if nil, returns nothing.
    def alert
      "<div id='alert'>#{flash[:alert]}</div>" if flash[:alert]
    end
    
    # Public: Hides an HTML element.
    # Useful in combination with inline true/false comparators.
    # 
    # Example
    # 
    #   <div style="<%= hidden unless session[:user] %>">Hello, world!</div>
    # 
    # Returns a String of CSS.
    def hidden
      'display: none;'
    end
    
    # Public: Converts a Date to select form fields.
    # All generated fields have classes of month_select, day_select, or year_select.
    # None of the options are required, but they are all recommended.
    # 
    #  select_name - name attr for fields, appended with '_day', '_month', or '_year'
    #    select_id - id attr for fields, appended with '_day', '_month', or '_year'
    # select_class - class attr for fields, appended with '_day', '_month', or '_year'
    #   start_year - first year to use in year select (default: 3 years ago)
    #     end_year - last year to use in year select (default: 3 years from now)
    #    day_first - Boolean to show the day before the month (default: false)
    #   month_name - Boolean to display month names (default: false)
    # 
    # Example
    # 
    #   date_select(
    #     DateTime.now
    #     select_class: 'your_class',
    #     select_id: 'your_id',
    #     select_name: 'your_name',
    #     start_year: 1999,
    #     end_year: 2021,
    #     day_first: true,
    #     month_name: false
    #   )
    # 
    # Returns select fields as a String.
    def date_select date, options = {}
      options[:select_name]  ||= ''
      options[:select_id]    ||= ''
      options[:select_class] ||= ''
      
      unless options[:select_id] == ''
        day_id   = " id='#{options[:select_id]}_day'"
        month_id = " id='#{options[:select_id]}_month'"
        year_id  = " id='#{options[:select_id]}_year'"
      else
        day_id   = ''
        month_id = ''
        year_id  = ''
      end
      
      unless options[:select_name] == ''
        day_name   = " name='#{options[:select_name]}_day'"
        month_name = " name='#{options[:select_name]}_month'"
        year_name  = " name='#{options[:select_name]}_year'"
      else
        day_name   = ''
        month_name = ''
        year_name  = ''
      end
      
      options[:day_first]    ||= false
      options[:start_year]   ||= Time.now.strftime('%Y').to_i - 3
      options[:end_year]     ||= Time.now.strftime('%Y').to_i + 3
      options[:month_name]   ||= false
      
      options[:start_year] = date.strftime('%Y').to_i if date.strftime('%Y').to_i < options[:start_year]
      options[:end_year]   = date.strftime('%Y').to_i if date.strftime('%Y').to_i > options[:end_year]
      
      months = [
        { num: '01', name: 'January'   },
        { num: '02', name: 'February'  },
        { num: '03', name: 'March'     },
        { num: '04', name: 'April'     },
        { num: '05', name: 'May'       },
        { num: '06', name: 'June'      },
        { num: '07', name: 'July'      },
        { num: '08', name: 'August'    },
        { num: '09', name: 'September' },
        { num: '10', name: 'October'   },
        { num: '11', name: 'November'  },
        { num: '12', name: 'December'  }
      ]
      
      day = "<select class='day_select #{options[:select_class]}'#{day_id}#{day_name}'>"
        (1..31).each do |d|
          day << "<option value='#{d}' #{'selected' if d == date.strftime('%d').to_i}>#{d}</option>"
        end
      day << "</select>"
      
      month = "<select class='month_select #{options[:select_class]}'#{month_id}#{month_name}>"
        months.each do |m|
          options[:month_name] ? d = m[:name] : d = m[:num]
          month << "<option value='#{m[:num]}' #{'selected' if m[:num] == date.strftime('%m')}>#{d}</option>"
        end    
      month << "</select>"
      
      year = "<select class='year_select #{options[:select_class]}'#{year_id}#{year_name}>"
        (options[:start_year]..options[:end_year]).each do |y|
          year << "<option value='#{y}' #{'selected' if y == date.strftime('%Y').to_i}>#{y}</option>"
        end
      year << "</select>"
      
      if options[:day_first]
        return "#{day} #{month} #{year}"
      else
        return "#{month} #{day} #{year}"
      end
    end
    
  end
  
  module ChassisExtras
    
    # Adds Rack::Flash to the app (requires sessions)
    def self.registered(app)
      app.enable :sessions
      app.set :session_secret, [*('A'..'Z')].sample(40).join if app.session_secret.nil?
      app.use Rack::Flash
    end
    
  end
  
  helpers  ChassisExtraHelpers
  register ChassisExtras
end
